package com.musollo.mp3tools.jaudiotagger;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.log4j.Logger;
import org.musicbrainz.JMBWSException;
import org.musicbrainz.Query;
import org.musicbrainz.model.Track;
import org.musicbrainz.webservice.filter.ReleaseFilter;
import org.musicbrainz.webservice.filter.TrackFilter;
import org.musicbrainz.wsxml.element.ReleaseSearchResults;
import org.musicbrainz.wsxml.element.TrackResult;
import org.musicbrainz.wsxml.element.TrackSearchResults;

import com.musollo.Configuration;
import com.musollo.exceptions.GenPuidException;
import com.musollo.mp3tools.AbstractMP3Updater;
import com.musollo.mp3tools.MP3Updater;
import com.musollo.mp3tools.UpdateCriteria;

/**
 * This class implements the IMP3Updater interface and uses the MusicBrainz
 * database to update the mp3 instances.
 * 
 * @see MP3Updater
 * @author paw.janssen
 * 
 * 
 *         TODO implement genre
 * 
 */
public class MusicBrainzUpdater extends AbstractMP3Updater {
	private static final Logger LOGGER = Logger.getLogger(MusicBrainzUpdater.class);

	/**
	 * The version of genpuid that must exist somewhere on the system.
	 */
	private static final String GENPUID_VERSION = "1.4";

	/**
	 * This is the time to wait before making a call to the MusicBrainz
	 * webservice, if more than 1 call is made per second, your IP will be
	 * blocked.
	 */
	private static final int MUSICBRAINZ_WEBSERVICE_PAUSE_TIME = 1500;

	private static int getAlbumTrackNumber(final Track t) {
		return (int) (long) t.getReleaseList().getReleases().get(0).getTrackList().getOffset() + 1;
	}

	private static String getReleaseYear(final Track t) {
		LOGGER.debug("Waiting 2 seconds before contacting MusicBrainz webservice");
		try {
			Thread.sleep(MUSICBRAINZ_WEBSERVICE_PAUSE_TIME);

			Query query = new Query();

			String albumTitle = t.getReleaseList().getReleases().get(0).getTitle();
			String artistTitle = t.getArtist().getName();

			ReleaseFilter rf = new ReleaseFilter();
			rf.setTitle(albumTitle);
			rf.setArtistName(artistTitle);

			ReleaseSearchResults releaseResults = query.getReleases(rf);

			LOGGER.info("Received " + releaseResults.getReleaseResults().size() + " albums that matched artist: "
					+ artistTitle + " & album: " + albumTitle);

			String date;

			if (releaseResults.getReleaseResults().size() > 0) {
				date = releaseResults.getReleaseResults().get(0).getRelease().getEarliestReleaseDate();
			} else {
				date = "";
			}

			return date;
		} catch (JMBWSException e) {
			LOGGER.error("Could not update track year information");
			LOGGER.debug(e);
		} catch (InterruptedException e) {
			LOGGER.error("Could not wait 2 seconds before contacting MusicBrainz, contact not made", e);
		} catch (NullPointerException e) {
			LOGGER.error("Could not update track year information");
			LOGGER.debug(e);
		}

		return null;
	}

	public MusicBrainzUpdater() throws GenPuidException {
		try {
			this.checkGenPuid();
		} catch (IOException e) {
			throw new GenPuidException("Couldn't read GenPuid", e);
		}
	}

	/**
	 * This method checks if genpuid (application for generating a puid) exists
	 * and is the correct version.
	 * 
	 * @throws IOException
	 *             - Thrown when genpuid could not be found.
	 * @throws GenPuidException
	 *             - Thrown when genpuid is not the correct version
	 */
	private void checkGenPuid() throws IOException, GenPuidException {
		ProcessBuilder genPuidProcessBuilder = new ProcessBuilder(Configuration.getInstance().getGenPuidLocation(),
				"-v");

		Process genPuidProcess = genPuidProcessBuilder.start();

		BufferedReader reader = new BufferedReader(new InputStreamReader(genPuidProcess.getInputStream()));

		String genPuidVersion = reader.readLine();

		reader.close();

		if (genPuidVersion == null) {
			throw new IOException("Couldn't find genpuid.exe at " + Configuration.getInstance().getGenPuidLocation());
		}

		if (!genPuidVersion.equals(MusicBrainzUpdater.GENPUID_VERSION)) {
			throw new GenPuidException("The version of your installed GenPuid is not "
					+ MusicBrainzUpdater.GENPUID_VERSION);
		} else {
			LOGGER.debug("Genpuid version " + MusicBrainzUpdater.GENPUID_VERSION);
		}
	}

	private TrackFilter createTrackFilter(final UpdateCriteria criteria) {
		// TODO implement year, genre and albumtracknumber

		// We don't do tracklength here, because jaudiotagger doesn't provide
		// length as accurate (milliseconds)
		TrackFilter trackFilter = new TrackFilter();

		boolean criteriaSet = false;

		for (Integer criteriaPriority : criteria) {
			if (criteriaPriority == UpdateCriteria.ALBUM && this.mp3ToUpdate.getAlbum() != null) {
				LOGGER.debug("Adding album to trackfilter: " + this.mp3ToUpdate.getAlbum());
				trackFilter.setReleaseTitle(this.mp3ToUpdate.getAlbum());
				criteriaSet = true;
			}
			if (criteriaPriority == UpdateCriteria.ALBUMTRACKNUMBER) {
				// trackFilter.setOffset((long)
				// this.mp3ToUpdate.getAlbumTrackNumber());
			}
			if (criteriaPriority == UpdateCriteria.ARTIST && this.mp3ToUpdate.getArtist() != null) {
				LOGGER.debug("Adding artist to trackfilter: " + this.mp3ToUpdate.getArtist());
				trackFilter.setArtistName(this.mp3ToUpdate.getArtist());
				criteriaSet = true;
			}
			if (criteriaPriority == UpdateCriteria.GENRE) {
				// trackFilter.setPuid(this.mp3ToUpdate.get());
			}
			if (criteriaPriority == UpdateCriteria.TITLE && this.mp3ToUpdate.getTitle() != null) {
				LOGGER.debug("Adding track title to trackfilter: " + this.mp3ToUpdate.getTitle());
				trackFilter.setTitle(this.mp3ToUpdate.getTitle());
				criteriaSet = true;
			}
			if (criteriaPriority == UpdateCriteria.YEAR) {
				// trackFilter.set(this.mp3ToUpdate.getPuid());
			}

		}

		if (!criteriaSet) {
			// If all else fails, generate a PUID and look up via the PUID
			if (this.mp3ToUpdate.getPuid() == null) {
				this.generatePUID();
			}

			LOGGER.debug("Adding PUID to trackfilter: " + this.mp3ToUpdate.getPuid());
			trackFilter.setPuid(this.mp3ToUpdate.getPuid());
		}

		return trackFilter;
	}

	private void generatePUID() {
		LOGGER.debug("Generating genpuid");

		String dnsKey = Configuration.getInstance().getDnsKey();

		try {
			LOGGER.debug("Starting genpuid: " + Configuration.getInstance().getGenPuidLocation() + " " + dnsKey + " "
					+ this.mp3ToUpdate.getMp3File().getAbsolutePath());
			ProcessBuilder genPuidProcessBuilder = new ProcessBuilder(Configuration.getInstance().getGenPuidLocation(),
					dnsKey, this.mp3ToUpdate.getMp3File().getAbsolutePath());

			Process genPuidProcess = genPuidProcessBuilder.start();

			BufferedReader reader = new BufferedReader(new InputStreamReader(genPuidProcess.getInputStream()));

			LOGGER.trace("Reading next line");
			String readLine = reader.readLine();

			String puid = null;

			while (readLine != null) {
				LOGGER.trace("Contents of read line: " + readLine);
				if (readLine.contains("puid: ")) {
					LOGGER.trace("Found puid in line: " + readLine);
					puid = readLine.replace("puid: ", "").trim();
					break;
				}

				LOGGER.trace("Reading next line");
				readLine = reader.readLine();
			}

			reader.close();

			LOGGER.debug("PUID generated: " + puid);

			this.mp3ToUpdate.setPuid(puid);

		} catch (IOException e) {
			LOGGER.warn("Couldn't start genpuid process for creating genpuids");
		}
	}

	@Override
	protected boolean updateTrack(final UpdateCriteria updateCriteria) {
		LOGGER.debug("Updating track");
		try {
			LOGGER.debug("Waiting 2 seconds before contacting MusicBrainz webservice");
			Thread.sleep(MUSICBRAINZ_WEBSERVICE_PAUSE_TIME);

			Query query = new Query();

			TrackSearchResults trackResults = query.getTracks(createTrackFilter(updateCriteria));

			LOGGER.info("Received " + trackResults.getTrackResults().size()
					+ " tracks that matched trackfilter from MusicBrainz");
			for (TrackResult tr : trackResults.getTrackResults()) {
				Track t = tr.getTrack();

				int musicBrainzLength = (int) Math.floor(t.getDuration() / 1000);
				int mp3Length = this.mp3ToUpdate.getLength();
				int difference = mp3Length - musicBrainzLength;

				if ((difference % LENGTH_OFFSET_FACTOR) != 0) {
					LOGGER.debug("Matched trackfilter song doesn't match tracklength. Original: "
							+ String.valueOf(mp3Length) + " Musicbrainz: " + String.valueOf(musicBrainzLength));
					LOGGER.trace("Difference: " + String.valueOf(difference) + " Offset: " + LENGTH_OFFSET_FACTOR);
					continue;
				}

				LOGGER.info("Found a possible match with " + t.toString());
				this.mp3ToUpdate.setTitle(t.getTitle());

				this.mp3ToUpdate.setAlbum(t.getReleases().get(0).getTitle());

				this.mp3ToUpdate.setArtist(t.getArtist().getName());

				this.mp3ToUpdate.setAlbumTrackNumber(getAlbumTrackNumber(t));

				this.mp3ToUpdate.setDate(getReleaseYear(t));

				return true;
			}
		} catch (InterruptedException e) {
			LOGGER.error("Could not wait 2 seconds before contacting MusicBrainz, contact not made", e);
		} catch (JMBWSException e) {
			LOGGER.error("Could not update track information");
			LOGGER.debug(e);
		}

		return false;
	}
}
